local skynet = require "skynet"
local runconfig = require "runconfig"

local rooms = {} -- { roomid : room { room_info, members } }
local timer_exp = {} -- { roomid : on_func }
local uid2room = {} -- { uid : room_id } -- 快速判断用户是否在房间

local function timer_exp_trigger(id)
    -- print("id = " .. id, " exp_trigger : " .. rooms[id].info.exp)

    local room = rooms[id]
    local members = room.members 

    if #members > 0 then 
        for _, userinfo_agent in pairs(members) do 
            skynet.send(userinfo_agent.agent, "lua", "notify_exp_change", room.info.exp)
            userinfo_agent.userinfo.exp = userinfo_agent.userinfo.exp +  room.info.exp -- 同步更新房间用户的经验值
        end 
    end 
end 

local function init_rooms()
    local mynode = skynet.getenv("node")
    local nodecfg = runconfig[mynode]
    for _, v in pairs(nodecfg.room or {}) do 
        local room_id = tonumber(v.id) 
        local room_name = v.name 
        local room_exp = tonumber(v.exp)
        local room_expinterval = tonumber(v.expinterval)

        local room = {
            members = {}, -- { { userinfo, agent } }
            info = {
                id = room_id,
                name = room_name,
                exp = room_exp,
                expinterval = room_expinterval,
            },
        }
        rooms[room_id] = room 

        timer_exp[room_id] = function() 
            timer_exp_trigger(room_id)

            skynet.timeout(room_expinterval * 100, timer_exp[room_id])
        end 
        skynet.timeout(room_expinterval * 100, timer_exp[room_id])
    end 
end 

-- return: uid, exp
local function get_manager(id)
    local user 

    if rooms[id] then 
        local member = rooms[id].members

        local max_exp = 0
        for _, userinfo_agent in pairs(member) do 
            if userinfo_agent.userinfo.exp > max_exp then 
                max_exp = userinfo_agent.userinfo.exp
                user = userinfo_agent.userinfo.uid
            end 
        end 
    end 

    return user, max_exp
end 

----------------------------------------------------------------------

local CMD = {} 

function CMD.enter_room(room_id, userinfo, agent)
    local member = {
        userinfo = userinfo,
        agent = agent,
    }
    -- rooms[room_id].members[#rooms[room_id].members + 1] = member
    table.insert(rooms[room_id].members, member)
    -- rooms[room_id].members[userinfo] = member -- 离开房间，应当通知所有玩家，无需映射

    for _, userinfo_agent in pairs(rooms[room_id].members) do 
        skynet.send(userinfo_agent.agent, "lua", "notify_user_enter", room_id, userinfo)
    end 

    uid2room[userinfo.uid] = room_id
    return true 
end 

function CMD.leave_room(room_id, uid)
    if not uid2room[uid] then 
        return true 
    end 

    local room = rooms[room_id]
    local member = room.members 

    local index
    local leave_user_info

    for idx, userinfo_agent in pairs(member) do 
        if userinfo_agent.userinfo.uid == uid then 
            index = idx 
            leave_user_info = userinfo_agent.userinfo 
            break 
        end 
    end 

    if index then 
        for _, userinfo_agent in pairs(member) do 
            skynet.send(userinfo_agent.agent, "lua", "notify_user_leave", room_id, leave_user_info)
        end 
        table.remove(member, index)

        uid2room[leave_user_info.uid] = nil 
        return true 
    end 
    return false 
end 

function CMD.list_members(room_id, uid)
    local result = {} 
    for _, userinfo_agent in pairs(rooms[room_id].members) do 
        local member = {
            userinfo = userinfo_agent.userinfo,
        }
        result[#result + 1] = member -- 这里member还是一张表，所以sproto解析，需要多一层，memberinfo。
        -- 当然也可以改直接插入userinfo
    end 
    return result
end 

function CMD.say_public(uid, msg)
    if not uid2room[uid] then 
        return false 
    end 
    local room_id = uid2room[uid]
    local room = rooms[room_id]
    local member = room.members 
    local touid = 0
    for _, userinfo_agent in pairs(member) do 
        skynet.send(userinfo_agent.agent, "lua", "notify_talking_message", {
            from_uid = uid, 
            to_uid = 0, 
            msg = msg,
        })
    end 
    return true
end 

function CMD.say_private(uid, to_uid, msg)
    if not uid2room[uid] or not uid2room[to_uid] then 
        return false
    end 

    -- 不同房间也行，或者 uid2room[uid] == uid2room[to_uid]

    local to_user 
    local room_id = uid2room[to_uid]
    local room = rooms[room_id]
    for _, userinfo_agent in pairs(room.members) do 
        if userinfo_agent.userinfo.uid == to_uid then 
            to_user = userinfo_agent.agent 
            break 
        end 
    end 

    if to_user then 
        skynet.send(to_user, "lua", "notify_talking_message", {
            from_uid = uid, 
            to_uid = to_uid, 
            msg = msg,
        })

        return true 
    end 
    return false 
end 

function CMD.kick(from_uid, to_uid)
    if not uid2room[from_uid] or not uid2room[to_uid] then 
        return false
    end 
    if uid2room[from_uid] ~= uid2room[to_uid] then 
        return false
    end 

    local room_id = uid2room[from_uid]
    local room = rooms[room_id]
    local manager, manager_exp = get_manager(room_id)
    if manager ~= from_uid then 
        return false
    end 

    
    local index 
    local kicked_user 
    for idx, userinfo_agent in pairs(room.members) do 
        if userinfo_agent.userinfo.uid == to_uid then 
            index = idx 
            kicked_user = userinfo_agent
            break 
        end 
    end 

    if index then 
        for idx, userinfo_agent in pairs(room.members) do 
            local agent = userinfo_agent.agent
            skynet.call(agent, "lua", "notify_user_leave", room_id, kicked_user.userinfo)
            skynet.call(agent, "lua", "notify_kick_message", {
                from = from_uid, 
                to = to_uid,
            })    
        end 
        table.remove(room.members, index)
        uid2room[to_uid] = nil 
        return true 
    end 
    return false 
end 

function CMD.send_exp(from_uid, to_uid, exp)
    if not uid2room[from_uid] or not uid2room[to_uid] then 
        return false
    end 
    if uid2room[from_uid] ~= uid2room[to_uid] then 
        return false
    end 
    
    local room_id = uid2room[from_uid]
    local room = rooms[room_id]
    local member = room.members 

    local from_userinfo_agent, to_userinfo_agent 
    for _, userinfo_agent in pairs(member) do 
        if userinfo_agent.userinfo.uid == from_uid then 
            from_userinfo_agent = userinfo_agent
        end 
        if userinfo_agent.userinfo.uid == to_uid then 
            to_userinfo_agent = userinfo_agent
        end 
        if from_userinfo_agent and to_userinfo_agent then 
            break 
        end 
    end 

    if not from_userinfo_agent or not to_userinfo_agent then 
        return false
    end 

    skynet.send(from_userinfo_agent.agent, "lua", "notify_exp_change", -exp)
    skynet.send(to_userinfo_agent.agent, "lua", "notify_exp_change", exp)
    from_userinfo_agent.userinfo.exp = from_userinfo_agent.userinfo.exp - exp 
    to_userinfo_agent.userinfo.exp = to_userinfo_agent.userinfo.exp + exp 

    local args = {
        from = from_uid, 
        to = to_uid, 
        exp = exp, 
    }

    for _, userinfo_agent in pairs(member) do 
        skynet.send(userinfo_agent.agent, "lua", "notify_send_exp_message", args)
    end 

    return true 
end 


skynet.start(function()
    skynet.dispatch("lua", function(_, _, cmd, ...)
        local f = assert(CMD[cmd])
        skynet.ret(skynet.pack(f(...)))
    end)

    init_rooms()
end)